//-------------------------------------------------------------------
//	Copyright (c) 2013  Zhirnov Andrey
//	This file is part of the "Noise Library" project.
//	See copyright notice in "linNoiseCL.h".
//-------------------------------------------------------------------

#pragma once

#include "../CSource.h"
#include "GeneratorUtils.h"

namespace Noise
{

	
	//
	// Function Source for Function Curve generation
	//

	class CSourceCurve : public CSource
	{
	// types
	protected:    
		struct ControlPoint
		{
			real inputValue;
			real outputValue;
			///
			ControlPoint () : inputValue(0), outputValue(0) {}
			ControlPoint (real inVal, real outVal) : inputValue(inVal), outputValue(outVal) {}
		};

		typedef CSourceCurve	Self;


	// variables
	protected:
		array< ControlPoint >	_controlPoints;
		CSourcePtr				_source;
		
		static const ESource	_sourceType = CURVE;


	// methods
	public:
		CSourceCurve ()
		{}

		~CSourceCurve ()
		{}

		ESourceState GetState () const
		{
			return _source.IsNotNull() ?
					( _controlPoints.Empty() ? UNCOMPLETE_DATA : SOURCE_COMPLETE)
					: UNCOMPLETE_ATTACHMENT_SOURCE;
		}
		
		virtual void Delete ()
		{
			_source = null;
			_controlPoints.ClearMem();
		}

		virtual EResult GetInteger (ESourceQuery type, int *v, int count) const
		{
			CHECK_ARGUMENT_R( count >= 1, INVALID_VALUE );

			switch ( type )
			{
				case SOURCE_STATE :
					v[0] = GetState();
					return OK;

				case SOURCE_TYPE :
					v[0] = _sourceType;
					return OK;

				case SOURCES_COUNT : {
					int src_count = 0;
					_source->GetInteger( type, &src_count, 1 );
					v[0] = src_count + 1;
					return OK;
				}

				case INPUT_SOURCES :
					v[0] = 1;
					return OK;

				case CONTROL_POINTS_COUNT :
					v[0] = _controlPoints.Count();
					return OK;
			}
			return INVALID_ENUM;
		}

		virtual EResult SetArg (EArgument type, CSourcePtr src)
		{
			switch ( type )
			{
				case SOURCE_0 :	_source = src;	return OK;
			}
			return INVALID_ENUM;
		}

		virtual EResult SetArg (EArgument type, const real *v, int count)
		{
			CHECK_ARGUMENT_R( IsEven(count), INVALID_VALUE );
			STATIC_ASSERTE( sizeof(ControlPoint) == sizeof(v[0])<<1 );

			switch ( type )
			{
				case ARG_ADD_CONTROL_POINTS :
					CHECK_ARGUMENT_R( count >= 2, INVALID_VALUE );
					return AddPoints( (ControlPoint *)v, count>>1 ) ? OK : INVALID_VALUE;

				case ARG_CONTROL_POINTS :
					_controlPoints.ClearMem();
					return AddPoints( (ControlPoint *)v, count>>1 ) ? OK : INVALID_VALUE;
			}
			return INVALID_ENUM;
		}

		virtual EResult GetArg (EArgument type, CSourcePtr &src) const
		{
			switch ( type )
			{
				case SOURCE_0 :	src = _source;	return OK;
			}
			return INVALID_ENUM;
		}
		
		virtual EResult GetArg (EArgument type, real *v, int count) const
		{
			CHECK_ARGUMENT_R( count >= 2 and IsEven(count), INVALID_VALUE );

			switch ( type )
			{
				case ARG_CONTROL_POINTS :
					MemCopy( v, _controlPoints.ptr(), Min( count * sizeof(real), _controlPoints.Size() ) );
					return OK;
			}
			return INVALID_ENUM;
		}

		bool AddPoints (const ControlPoint *points, int count)
		{
			bool result = true;

			for (int i = 0; i < count; ++i)
			{
				int pos = FindInsertionPos( points[i].inputValue );
				
				if ( pos == -1 )
					result = false;

				_controlPoints.Insert( points[i], pos );
			}
			return result;
		}
		
		int FindInsertionPos (real inputValue) const
		{
			uint insertionPos;
			for (insertionPos = 0; insertionPos < _controlPoints.Count(); ++insertionPos)
			{
				if ( inputValue < _controlPoints[insertionPos].inputValue )		break;		else
				if ( inputValue == _controlPoints[insertionPos].inputValue )	return -1;
			}
			return insertionPos;
		}
		
		virtual CSourcePtr Clone () const override
		{
			Self *	src = new Self();
			src->_controlPoints = _controlPoints;
			return src;
		}
		
		virtual void PreBuild (const TPreBuildData &data) override
		{
			data.Push( _source.ptr() );
		}

		virtual bool BuildFunction (TFunctionData &data) override
		{
			Utils::CubicMixFunction( data );

			if ( data.AddSourceType( _sourceType ) )
			{
				data.source << TOSTRING(
					real Curve (real3 vPoint, real value, const real2 controlPoints[], int controlPointsCount)\n
					{\n
						int indexPos = 0;\n
						for (; indexPos < controlPointsCount; ++indexPos) {\n
							if ( value < controlPoints[indexPos].x )\n
								break;\n
						}\n
						\n
						int index0 = clamp( indexPos - 2, 0, controlPointsCount - 1 );\n
						int index1 = clamp( indexPos - 1, 0, controlPointsCount - 1 );\n
						int index2 = clamp( indexPos    , 0, controlPointsCount - 1 );\n
						int index3 = clamp( indexPos + 1, 0, controlPointsCount - 1 );\n
						\n
						if ( index1 == index2 ) {\n
							return controlPoints[index1].y;\n
						}\n
						  \n
						real input0 = controlPoints[index1].x;\n
						real input1 = controlPoints[index2].x;\n
						real alpha  = (value - input0) / (input1 - input0);\n
						\n
						return CubicMix( controlPoints[index0].y, controlPoints[index1].y,\n
										 controlPoints[index2].y, controlPoints[index3].y, alpha );\n
					}\n\n
				);
			}

			CHECK_VALUE( not _controlPoints.Empty() );

			data.source
				<< "real " << CSource::FunctionName() << _id << " (real3 vPoint)\n{\n"
				<< "const real2 controlPoints[" << _controlPoints.Count() << "] = { ";

			FOR( i, _controlPoints )
			{
				data.source
					<< "\n(real2)( " << _controlPoints[i].inputValue << ", "
					<< _controlPoints[i].outputValue << " ),";
			}
			data.source.PopBack();
			data.source << "\n};\nconst real value = ";

			CSource::BuildFunctionCall( data.source, _source->GetSourceId() ) << ";\n"
				<< "const int controlPointsCount = " << _controlPoints.Count() << ";\n"
				<< "return Curve( vPoint, value, controlPoints, controlPointsCount );\n}\n";

			return true;
		}
		
		NATIVE(
		template < typename Real, Real (CSource::*NativeFunction)(const TVec<Real,3> &) >
		Real NativeFunction (const TVec<Real,3> &vPoint)
		{
			CHECK_VALUE_R( not _controlPoints.Empty(), Real() );

			const Real	value = (_source.ptr()->*NativeFunction)( vPoint );

			uint indexPos = 0;
			FORc( indexPos, _controlPoints ) {
				if ( value < _controlPoints[indexPos].inputValue ) {
					break;
				}
			}

			int index0 = Clamp( indexPos - 2, 0, _controlPoints.Count() - 1 );
			int index1 = Clamp( indexPos - 1, 0, _controlPoints.Count() - 1 );
			int index2 = Clamp( indexPos    , 0, _controlPoints.Count() - 1 );
			int index3 = Clamp( indexPos + 1, 0, _controlPoints.Count() - 1 );

			if ( index1 == index2 ) {
				return _controlPoints[index1].outputValue;
			}
  
			Real input0 = _controlPoints[index1].inputValue;
			Real input1 = _controlPoints[index2].inputValue;
			Real alpha  = (value - input0) / (input1 - input0);

			return CubicMix<Real>(	_controlPoints[index0].outputValue, _controlPoints[index1].outputValue,
									_controlPoints[index2].outputValue, _controlPoints[index3].outputValue, alpha );
		}

		virtual float32 NativeFunction32 (const fvec3 &vPoint) override
		{
			return NativeFunction< float32, &CSource::NativeFunction32 >( vPoint );
		}

		virtual float64 NativeFunction64 (const dvec3 &vPoint) override
		{
			return NativeFunction< float64, &CSource::NativeFunction64 >( vPoint );
		})

		static bool CreateSource (CSourcePtr &src, CContext *context, ESource type)
		{
			ASSERT( type == _sourceType );

			src = new Self();
			return true;
		}
	};

}	// Noise